#include "qrender/box_renderer.h"
#include <GL/gl.h>

#include <QOpenGLContext>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>

#include <QtMath>

#include <qrender/camera.h>

#define TEXTURE_UNIT 0

BoxRenderer::BoxRenderer() : Renderer()
{
    generateBoxPlaneIndices();
    generateBoxLineIndices();
}

BoxRenderer::~BoxRenderer()
{
    BoxRenderer::release();
}

void BoxRenderer::setPose(const std::vector<double> &pose)
{
    if (pose_ == pose) {
        return;
    }

    pose_ = pose;
}

void BoxRenderer::setShape(float length, float width, float height,
                           BoxType type)
{
    if (x_len_ == length && y_len_ == width && z_len_ == height &&
        type_ == type) {
        return;
    }

    x_len_ = length;
    y_len_ = width;
    z_len_ = height;

    type_ = type;

    data_changed_ = true;
}

void BoxRenderer::setColor(const QVector4D &color)
{
    if (color_ == color) {
        return;
    }

    color_ = color;

    texture_path_.clear();
    texture_changed_ = true;
}

void BoxRenderer::setTexture(const QString &texture_path)
{
    if (texture_path_ == texture_path) {
        return;
    }

    texture_path_ = texture_path;

    texture_changed_ = true;
}

void BoxRenderer::create()
{
    vao_color_.reset(new QOpenGLVertexArrayObject());
    vao_color_->create();

    vao_texture_.reset(new QOpenGLVertexArrayObject());
    vao_texture_->create();

    // 生成顶点数据
    generateTexture();

    vbo_.reset(new QOpenGLBuffer());
    vbo_->create();
    vbo_->bind();

    generateColorProgram();
    generateTextureProgram();

    vbo_->release();
}

void BoxRenderer::render(const CameraInfo &camera)
{
    // 更新数据
    if (data_changed_) {
        generateBoxVertices();

        data_changed_ = false;
    }

    if (texture_changed_) {
        generateTexture();

        texture_changed_ = false;
    }

    if (!texture_ || type_ == BoxType_Line) {
        // 透明不渲染
        // TODO: 有透明度在后面渲染
        if (color_[3] == 0) {
            return;
        }
    }

    // 更新 OpenGL 配置
    auto f = QOpenGLContext::currentContext()->functions();
    f->glEnable(GL_DEPTH_TEST);
    f->glEnable(GL_LINE_SMOOTH);               // 设置反走样
    f->glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); // 设置反走样
    if (x_len_ != 0 && y_len_ != 0 && z_len_ != 0) {
        // 有长宽高才禁用背面剔除, 如果长宽高某个为 0, 则渲染的不是立方体,
        // 而是平面
        f->glDisable(GL_CULL_FACE); // 禁用背面剔除
    }

    // 绑定着色器和缓冲区到当前上下文
    if (!texture_ || type_ == BoxType_Line) {
        program_color_->bind();
    } else {
        program_texture_->bind();
    }
    vbo_->bind();

    // 相机参数
    if (camera.valid) {
        if (!texture_ || type_ == BoxType_Line) {
            program_color_->setUniformValue("view_matrix", camera.view_matrix);
            program_color_->setUniformValue("projection_matrix",
                                            camera.projection_matrix);
        } else {
            program_texture_->setUniformValue("view_matrix",
                                              camera.view_matrix);
            program_texture_->setUniformValue("projection_matrix",
                                              camera.projection_matrix);
        }
    }

    // 模型数据
    QMatrix4x4 model_matrix;
    model_matrix.translate(QVector3D(pose_[0], pose_[1], pose_[2]));
    model_matrix.rotate(qRadiansToDegrees(pose_[5]), 0.0, 0.0, 1.0);
    model_matrix.rotate(qRadiansToDegrees(pose_[4]), 0.0, 1.0, 0.0);
    model_matrix.rotate(qRadiansToDegrees(pose_[3]), 1.0, 0.0, 0.0);

    if (!texture_ || type_ == BoxType_Line) {
        program_color_->setUniformValue("model_matrix", model_matrix);
        program_color_->setUniformValue("f_color", color_);
    } else {
        program_texture_->setUniformValue("model_matrix", model_matrix);
    }

    {
        vbo_->allocate(vertices_.constData(),
                       vertices_.size() * sizeof(GLfloat));

        switch (type_) {
        case BoxType_Plane:
            // 颜色
            if (!texture_) {
                QOpenGLVertexArrayObject::Binder vao_binder(vao_color_.get());
                f->glDrawElements(GL_TRIANGLES, plane_indices_.size(),
                                  GL_UNSIGNED_INT, plane_indices_.constData());
            }
            // 纹理
            else {
                program_texture_->setUniformValue("f_texture", TEXTURE_UNIT);
                texture_->bind(TEXTURE_UNIT);
                QOpenGLVertexArrayObject::Binder vao_binder(vao_texture_.get());
                for (int i = 0; i < 6; i++) {
                    f->glDrawArrays(GL_TRIANGLE_STRIP, i * 5, 5);
                }
            }
            break;
        case BoxType_Line: {
            QOpenGLVertexArrayObject::Binder vao_binder(vao_color_.get());
            f->glLineWidth(2.0);
            f->glDrawElements(GL_LINES, line_indices_.size(), GL_UNSIGNED_INT,
                              line_indices_.constData());
            break;
        }
        default:
            break;
        }
    }

    vbo_->release();
    if (!texture_ || type_ == BoxType_Line) {
        program_color_->release();
    } else {
        program_texture_->release();
    }
}

void BoxRenderer::release()
{
    if (texture_) {
        texture_->release();
        texture_.reset();
    }

    vao_color_.reset();
    vao_texture_.reset();
    vbo_.reset();

    program_color_.reset();
    program_texture_.reset();
}

void BoxRenderer::generateBoxVertices()
{
    vertices_.clear();

    // 中心点偏移
    QVector3D center(0, 0, 0);

    QVector3D half_extents(x_len_ / 2.0f, y_len_ / 2.0f, z_len_ / 2.0f);

    // 用颜色
    if (texture_path_.isEmpty() || type_ == BoxType_Line) {
        vertices_ = {
            // 前面
            center.x() - half_extents.x(), center.y() - half_extents.y(),
            center.z() + half_extents.z(),

            center.x() + half_extents.x(), center.y() - half_extents.y(),
            center.z() + half_extents.z(),

            center.x() + half_extents.x(), center.y() + half_extents.y(),
            center.z() + half_extents.z(),

            center.x() - half_extents.x(), center.y() + half_extents.y(),
            center.z() + half_extents.z(),

            // 后面
            center.x() - half_extents.x(), center.y() - half_extents.y(),
            center.z() - half_extents.z(),

            center.x() + half_extents.x(), center.y() - half_extents.y(),
            center.z() - half_extents.z(),

            center.x() + half_extents.x(), center.y() + half_extents.y(),
            center.z() - half_extents.z(),

            center.x() - half_extents.x(), center.y() + half_extents.y(),
            center.z() - half_extents.z()
        };
    }
    // 用纹理
    else {
        QVector<QVector3D> v = { // 前面
                                 QVector3D(center.x() - half_extents.x(),
                                           center.y() - half_extents.y(),
                                           center.z() + half_extents.z()),

                                 QVector3D(center.x() + half_extents.x(),
                                           center.y() - half_extents.y(),
                                           center.z() + half_extents.z()),

                                 QVector3D(center.x() + half_extents.x(),
                                           center.y() + half_extents.y(),
                                           center.z() + half_extents.z()),

                                 QVector3D(center.x() - half_extents.x(),
                                           center.y() + half_extents.y(),
                                           center.z() + half_extents.z()),

                                 // 后面
                                 QVector3D(center.x() - half_extents.x(),
                                           center.y() - half_extents.y(),
                                           center.z() - half_extents.z()),

                                 QVector3D(center.x() + half_extents.x(),
                                           center.y() - half_extents.y(),
                                           center.z() - half_extents.z()),

                                 QVector3D(center.x() + half_extents.x(),
                                           center.y() + half_extents.y(),
                                           center.z() - half_extents.z()),

                                 QVector3D(center.x() - half_extents.x(),
                                           center.y() + half_extents.y(),
                                           center.z() - half_extents.z())
        };

        vertices_ = { // 正面
                      // position                   // texture
                      v[0].x(), v[0].y(), v[0].z(), 0.0f, 0.0f,

                      v[1].x(), v[1].y(), v[1].z(), 1.0f, 0.0f,

                      v[2].x(), v[2].y(), v[2].z(), 1.0f, 1.0f,

                      v[3].x(), v[3].y(), v[3].z(), 0.0f, 1.0f,

                      v[0].x(), v[0].y(), v[0].z(), 0.0f, 0.0f,

                      // 后面
                      v[4].x(), v[4].y(), v[4].z(), 0.0f, 0.0f,

                      v[5].x(), v[5].y(), v[5].z(), 1.0f, 0.0f,

                      v[6].x(), v[6].y(), v[6].z(), 1.0f, 1.0f,

                      v[7].x(), v[7].y(), v[7].z(), 0.0f, 1.0f,

                      v[4].x(), v[4].y(), v[4].z(), 0.0f, 0.0f,

                      // 右面
                      v[1].x(), v[1].y(), v[1].z(), 0.0f, 0.0f,

                      v[5].x(), v[5].y(), v[5].z(), 1.0f, 0.0f,

                      v[6].x(), v[6].y(), v[6].z(), 1.0f, 1.0f,

                      v[2].x(), v[2].y(), v[2].z(), 0.0f, 1.0f,

                      v[1].x(), v[1].y(), v[1].z(), 0.0f, 0.0f,

                      // 左面
                      v[0].x(), v[0].y(), v[0].z(), 0.0f, 0.0f,

                      v[4].x(), v[4].y(), v[4].z(), 1.0f, 0.0f,

                      v[7].x(), v[7].y(), v[7].z(), 1.0f, 1.0f,

                      v[3].x(), v[3].y(), v[3].z(), 0.0f, 1.0f,

                      v[0].x(), v[0].y(), v[0].z(), 0.0f, 0.0f,

                      // 上面
                      v[3].x(), v[3].y(), v[3].z(), 0.0f, 0.0f,

                      v[2].x(), v[2].y(), v[2].z(), 1.0f, 0.0f,

                      v[6].x(), v[6].y(), v[6].z(), 1.0f, 1.0f,

                      v[7].x(), v[7].y(), v[7].z(), 0.0f, 1.0f,

                      v[3].x(), v[3].y(), v[3].z(), 0.0f, 0.0f,

                      // 下面
                      v[0].x(), v[0].y(), v[0].z(), 0.0f, 0.0f,

                      v[1].x(), v[1].y(), v[1].z(), 1.0f, 0.0f,

                      v[5].x(), v[5].y(), v[5].z(), 1.0f, 1.0f,

                      v[4].x(), v[4].y(), v[4].z(), 0.0f, 1.0f,

                      v[0].x(), v[0].y(), v[0].z(), 0.0f, 0.0f
        };
    }
}

void BoxRenderer::generateBoxPlaneIndices()
{
    plane_indices_.clear();

    plane_indices_ = {
        0, 1, 2, 2, 3, 0, // 前面
        4, 5, 6, 6, 7, 4, // 后面
        1, 5, 6, 6, 2, 1, // 右面
        0, 4, 7, 7, 3, 0, // 左面
        3, 2, 6, 6, 7, 3, // 上面
        0, 1, 5, 5, 4, 0, // 下面
    };
}

void BoxRenderer::generateBoxLineIndices()
{
    line_indices_.clear();

    line_indices_ = {
        0, 1, 1, 2, 2, 3, 3, 0, // 前面
        4, 5, 5, 6, 6, 7, 7, 4, // 后面
        0, 4, 1, 5, 2, 6, 3, 7  // 前后连接
    };
}

void BoxRenderer::generateTexture()
{
    if (texture_path_.isEmpty() || type_ == BoxType_Line) {
        if (texture_) {
            texture_->release();
            texture_.reset();
        }
    } else {
        if (texture_) {
            texture_->release();
        }

        texture_.reset(new QOpenGLTexture(QImage(texture_path_).mirrored()));
        texture_->setWrapMode(QOpenGLTexture::DirectionS,
                              QOpenGLTexture::Repeat);
        texture_->setWrapMode(QOpenGLTexture::DirectionT,
                              QOpenGLTexture::Repeat);
        texture_->setMinificationFilter(QOpenGLTexture::Linear);
        texture_->setMagnificationFilter(QOpenGLTexture::Linear);

        // 可能是 texture_path 无效, 不排除其他问题.
        if (!texture_->create()) {
            qCritical() << "QOpenGLTexture::create() failed! texture_path:"
                        << texture_path_;

            texture_->release();
            texture_.reset();

            texture_path_.clear();
        }
    }
}

void BoxRenderer::generateColorProgram()
{
    // 顶点着色器
    const char *vsrc_color = R"(
#version 330

in vec3 v_pos;

uniform mat4 model_matrix;
uniform mat4 view_matrix;
uniform mat4 projection_matrix;

void main()
{
    gl_Position = projection_matrix * view_matrix * model_matrix * vec4(v_pos, 1.0);
}
)";
    // 片段着色器
    const char *fsrc_color = R"(
#version 330 core

uniform vec4 f_color;

out vec4 frag_color;

void main()
{
    frag_color = f_color;
}
)";

    // 创建着色器程序
    program_color_.reset(new QOpenGLShaderProgram());
    program_color_->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,
                                                     vsrc_color); // 顶点着色器
    program_color_->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,
                                                     fsrc_color); // 片段着色器
    program_color_->link();
    program_color_->bind();

    program_color_->setUniformValue("view_matrix", QMatrix4x4());
    program_color_->setUniformValue("projection_matrix", QMatrix4x4());
    program_color_->setUniformValue("f_color", color_);

    {
        QOpenGLVertexArrayObject::Binder vao_binder(vao_color_.get());
        GLuint v_pos = program_color_->attributeLocation("v_pos");
        program_color_->setAttributeBuffer(v_pos, GL_FLOAT, 0, 3,
                                           3 * sizeof(GLfloat));
        program_color_->enableAttributeArray(v_pos);
    }

    program_color_->release();
}

void BoxRenderer::generateTextureProgram()
{
    // 顶点着色器
    const char *vsrc_texture = R"(
#version 330

in vec3 v_pos;
in vec2 v_tex_coord;

out vec2 f_tex_coord;

uniform mat4 model_matrix;
uniform mat4 view_matrix;
uniform mat4 projection_matrix;

void main()
{
    gl_Position = projection_matrix * view_matrix * model_matrix * vec4(v_pos, 1.0);
    f_tex_coord = v_tex_coord;
}
)";
    // 片段着色器
    const char *fsrc_texture = R"(
#version 330 core

in vec2 f_tex_coord;

uniform sampler2D f_texture; // 纹理采样器

out vec4 frag_color;

void main()
{
    frag_color = texture(f_texture, f_tex_coord);
}
)";

    // 创建着色器程序
    program_texture_.reset(new QOpenGLShaderProgram());
    program_texture_->addCacheableShaderFromSourceCode(
        QOpenGLShader::Vertex,
        vsrc_texture); // 顶点着色器
    program_texture_->addCacheableShaderFromSourceCode(
        QOpenGLShader::Fragment,
        fsrc_texture); // 片段着色器
    program_texture_->link();
    program_texture_->bind();

    program_texture_->setUniformValue("view_matrix", QMatrix4x4());
    program_texture_->setUniformValue("projection_matrix", QMatrix4x4());
    program_texture_->setUniformValue("f_texture", TEXTURE_UNIT);

    {
        QOpenGLVertexArrayObject::Binder vao_binder(vao_texture_.get());
        GLuint v_pos = program_texture_->attributeLocation("v_pos");
        program_texture_->setAttributeBuffer(v_pos, GL_FLOAT, 0, 3,
                                             5 * sizeof(GLfloat));
        program_texture_->enableAttributeArray(v_pos);

        GLuint v_tex_coord = program_texture_->attributeLocation("v_tex_coord");
        program_texture_->setAttributeBuffer(
            v_tex_coord, GL_FLOAT, 3 * sizeof(GLfloat), 2, 5 * sizeof(GLfloat));
        program_texture_->enableAttributeArray(v_tex_coord);
    }

    program_texture_->release();
}
